package contrib

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"github.com/modern-go/reflect2"
	"github.com/pkg/errors"
	"go.uber.org/zap"
	"myCloud/constant"
	"myCloud/dbmodel"
	"myCloud/dbopr"
	"myCloud/logsvr"
	"myCloud/utils"
	"net/http"
	"strconv"
	"strings"
	"time"
)

// gin contrib 被web或 webv2/v3... 依赖
// 可以调用/依赖 dbopr，devmodel等库的内容

var (
	logger = logsvr.GetLogger("contrib")
)

func GinZapMiddleware(_logger *zap.Logger) gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		// some evil middlewares modify this values
		path := c.Request.URL.Path
		query := c.Request.URL.RawQuery

		var keys logsvr.LogFields
		c.Set(constant.LogFields, &keys)
		c.Next()

		end := time.Now()
		latency := end.Sub(start)

		if len(c.Errors) > 0 {
			// Append error field if this is an erroneous request.
			for _, e := range c.Errors.Errors() {
				_logger.Error(e)
			}
		}

		fields := []zap.Field{
			zap.Int("status", c.Writer.Status()),
			zap.String("method", c.Request.Method),
			zap.String("query", query),
			zap.String("ip", c.ClientIP()),
			zap.String("user-agent", c.Request.UserAgent()),
			zap.String("time", utils.Timetostr(end)),
			zap.Duration("latency", latency),
		}
		if len(keys.GetFields()) > 0 {
			fields = append(fields, keys.GetFields()...)
		}
		// 输出每个请求的reqid，用于适配终端统计结果
		reqid := c.Request.Header.Get("ReqID")
		if reqid != "" {
			fields = append(fields, zap.String("ReqID", reqid))
		}

		_logger.Info(path, fields...)
	}
}

// response userID, token, error
func parseAuthHeaders(h string) (string, string, error) {
	header := strings.Trim(h, " \t\r\n")
	res := strings.Split(header, " ")
	if len(res) != 3 {
		return "", "", fmt.Errorf("cannot parse http headers %v", header)
	}
	if res[0] != "USER" {
		return "", "", fmt.Errorf("请求头参数格式无法识别")
	}

	return res[1], res[2], nil
}

func UserAuthorization() gin.HandlerFunc {
	return func(c *gin.Context) {
		var err error
		var token string
		var userID string

		authHeader := c.GetHeader("Authorization")

		// 如果header中没有传认证参数，则考虑从query参数中取
		if authHeader != "" {
			userID, token, err = parseAuthHeaders(authHeader)
		} else {
			userID = c.Query("user_id")
			token = c.Query("access_token")
			if userID == "" || token == "" {
				err = errors.New("authorization info get failed")
			}
		}
		if err != nil {
			AbortReq(c, http.StatusUnauthorized, "请求头解析失败", err)
			return
		}

		storageUserSession(c, userID, token)

		//processing
		c.Next()
	}
}

func storageUserSession(c *gin.Context, userId, token string) {
	p := dbopr.NewUserProvider()
	user, tokenObj, err := p.CheckUserToken(userId, token)
	if err != nil {
		AbortReq(c, http.StatusUnauthorized, "查询用户信息执行数据库异常", err)
		return
	}

	c.Set(keyUserId.String(), userId)
	c.Set(keyToken.String(), token)
	c.Set(keyTokenObject.String(), tokenObj)
	c.Set(keyUser.String(), user)

	// set log field.
	keys := c.MustGet(constant.LogFields).(*logsvr.LogFields)
	keys.AddField(zap.String("userId", userId))
}

func GetUser(c *gin.Context) *dbmodel.User {
	return c.MustGet(keyUser.String()).(*dbmodel.User)
}

func GetUserToken(c *gin.Context) *dbmodel.UserToken {
	return c.MustGet(keyTokenObject.String()).(*dbmodel.UserToken)
}

// pageArgumentsMiddleware 分页 middleware，默认自1起，每页 8 个
func PaginationMiddleware(defaultPage, defaultCount int) gin.HandlerFunc {
	return func(c *gin.Context) {
		var page, count int
		var err error
		page_ := c.DefaultQuery("page", strconv.Itoa(defaultPage))
		count_ := c.DefaultQuery("count", strconv.Itoa(defaultCount))
		page, err = strconv.Atoi(page_)
		if err != nil {
			AbortReq(c, http.StatusBadRequest, "page format error.", err)
			return
		}
		count, err = strconv.Atoi(count_)
		if err != nil {
			AbortReq(c, http.StatusBadRequest, "count format error.", err)
			return
		}
		if page <= 0 || count <= 0 {
			err = fmt.Errorf("page/count small than 0 %d, %d", page, count)
			AbortReq(c, http.StatusBadRequest, "分页参数错误", err)
			return
		}

		c.Set(keyPageNum.String(), page)
		c.Set(keyPageCount.String(), count)

		c.Next()
	}
}

func GetPagination(c *gin.Context) (int, int) {
	num := c.MustGet(keyPageNum.String()).(int)
	count := c.MustGet(keyPageCount.String()).(int)
	return num, count
}

func ArgsCheck(obj interface{}) gin.HandlerFunc {
	t := reflect2.TypeOf(obj)
	if t == nil {
		logger.Errorf("unknown reflect type, error")
	}
	var validate = validator.New()
	return func(c *gin.Context) {
		ptr := t.New()
		err := c.Bind(ptr)
		if err != nil {
			AbortReq(c, http.StatusBadRequest, "请求参数不正确", err)
			return
		}

		err = validate.Struct(ptr)
		if err != nil {
			AbortReq(c, http.StatusBadRequest, "请求参数错误，校验失败", err)
			return
		}

		c.Set(keyRequestObject.String(), ptr)
		c.Next()
	}
}

func GetRequestObject(c *gin.Context) interface{} {
	return c.MustGet(keyRequestObject.String())
}

func SetDeviceInfo(c *gin.Context) {
	devID, err := strconv.Atoi(c.Param("devID"))
	if err != nil {
		AbortReq(c, http.StatusBadRequest, "devID 格式错误", err)
		return
	}

	user := GetUser(c)
	p := dbopr.NewUserDeviceProvider()
	rs, notFound, err := p.GetById(devID)
	if err != nil {
		AbortReq(c, http.StatusInternalServerError, "获取终端出错", err)
		return
	}
	if notFound {
		AbortReq(c, http.StatusNotFound, "未找到指定终端")
		return
	}
	if uint(rs.UserID) != user.ID {
		logger.Warnw("权限错误", zap.Int("userId", rs.UserID))
		AbortReq(c, http.StatusNotFound, "未找到指定终端")
		return
	}

	c.Set(keyUserDevice.String(), rs)
	c.Next()
}

func GetUserDevice(c *gin.Context) *dbmodel.UserDevice {
	return c.MustGet(keyUserDevice.String()).(*dbmodel.UserDevice)
}

func SetAlbumInfo(c *gin.Context) {
	albumID, err := strconv.Atoi(c.Param("albumID"))
	if err != nil {
		AbortReq(c, http.StatusBadRequest, "albumID 格式错误", err)
		return
	}

	user := GetUser(c)
	p := dbopr.NewAlbumProvider()
	rs, notFound, err := p.GetById(albumID)
	if err != nil {
		AbortReq(c, http.StatusInternalServerError, "获取相册出错", err)
		return
	}
	if notFound {
		AbortReq(c, http.StatusNotFound, "未找到指定相册")
		return
	}
	if uint(rs.UserID) != user.ID {
		logger.Warnw("权限错误", zap.Int("userId", rs.UserID))
		AbortReq(c, http.StatusNotFound, "未找到指定相册")
		return
	}

	c.Set(keyAlbum.String(), rs)
	c.Next()
}

func GetUserAlbum(c *gin.Context) *dbmodel.Album {
	return c.MustGet(keyAlbum.String()).(*dbmodel.Album)
}

func SetFileInfo(c *gin.Context) {
	fileID, err := strconv.Atoi(c.Param("fileID"))
	if err != nil {
		AbortReq(c, http.StatusBadRequest, "fileID 格式错误", err)
		return
	}

	user := GetUser(c)
	p := dbopr.NewFileProvider()
	rs, notFound, err := p.GetById(fileID)
	if err != nil {
		AbortReq(c, http.StatusInternalServerError, "获取文件出错", err)
		return
	}
	if notFound {
		AbortReq(c, http.StatusNotFound, "未找到指定文件")
		return
	}
	if uint(rs.UserID) != user.ID {
		logger.Warnw("权限错误", zap.Int("userId", rs.UserID))
		AbortReq(c, http.StatusNotFound, "未找到指定文件")
		return
	}

	c.Set(keyFile.String(), rs)
	c.Next()
}

func GetUserFile(c *gin.Context) *dbmodel.File {
	return c.MustGet(keyFile.String()).(*dbmodel.File)
}
